import tkinter as tk
from tkinter import filedialog, ttk, messagebox
from moviepy.editor import VideoFileClip, CompositeVideoClip
from PIL import Image, ImageTk
import os
import threading
import time

# 添加moviepy补丁
from moviepy.config import change_settings
change_settings({"IMAGEMAGICK_BINARY": "auto"})

# 修复ANTIALIAS错误
try:
    from PIL import Image
    Image.ANTIALIAS = Image.Resampling.LANCZOS
except (ImportError, AttributeError):
    pass

class VideoToGifConverter:
    def __init__(self, root):
        self.root = root
        self.root.title("视频转GIF工具")
        self.root.geometry("800x700")
        
        # 设置窗口最小尺寸
        self.root.minsize(600, 500)
        
        # 配置根窗口的网格权重，使其可以自适应调整
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_columnconfigure(0, weight=1)
        
        # 设置现代风格
        self.style = ttk.Style()
        self.style.configure("TFrame", background="#f0f0f0")
        self.style.configure("TLabel", background="#f0f0f0", font=("Microsoft YaHei UI", 10))
        self.style.configure("TButton", font=("Microsoft YaHei UI", 10))
        self.style.configure("TLabelframe", background="#f0f0f0")
        self.style.configure("TLabelframe.Label", background="#f0f0f0", font=("Microsoft YaHei UI", 10))
        self.style.configure("TEntry", font=("Microsoft YaHei UI", 10))
        
        # 设置窗口背景色
        self.root.configure(bg="#f0f0f0")
        
        # 创建主框架并设置其权重
        self.main_frame = ttk.Frame(self.root, padding="20")
        self.main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        self.main_frame.grid_columnconfigure(0, weight=1)
        
        # 标题
        title_label = ttk.Label(self.main_frame, text="视频转GIF工具", font=("Microsoft YaHei UI", 24, "bold"))
        title_label.grid(row=0, column=0, columnspan=3, pady=(0, 30))
        
        # 文件选择区域
        file_frame = ttk.Frame(self.main_frame)
        file_frame.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=5)
        file_frame.grid_columnconfigure(1, weight=1)  # 让文件路径输入框可以自适应宽度
        
        # 视频文件选择
        ttk.Label(file_frame, text="选择视频文件:", font=("Microsoft YaHei UI", 10)).grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
        self.video_path = tk.StringVar()
        video_entry = ttk.Entry(file_frame, textvariable=self.video_path, font=("Microsoft YaHei UI", 10))
        video_entry.grid(row=0, column=1, sticky=(tk.W, tk.E), padx=5)
        browse_btn = tk.Button(file_frame, text="浏览", command=self.browse_video, 
                             bg="#e0e0e0", fg="#000000", 
                             font=("Microsoft YaHei UI", 9),
                             relief="flat", width=8, height=1)
        browse_btn.grid(row=0, column=2, padx=(5, 0))
        
        # 输出路径选择
        ttk.Label(file_frame, text="保存位置:", font=("Microsoft YaHei UI", 10)).grid(row=1, column=0, sticky=tk.W, padx=(0, 10), pady=(10, 0))
        self.output_path = tk.StringVar()
        output_entry = ttk.Entry(file_frame, textvariable=self.output_path, font=("Microsoft YaHei UI", 10))
        output_entry.grid(row=1, column=1, sticky=(tk.W, tk.E), padx=5, pady=(10, 0))
        save_btn = tk.Button(file_frame, text="浏览", command=self.browse_output,
                           bg="#e0e0e0", fg="#000000",
                           font=("Microsoft YaHei UI", 9),
                           relief="flat", width=8, height=1)
        save_btn.grid(row=1, column=2, padx=(5, 0), pady=(10, 0))
        
        # 创建设置框架
        settings_frame = ttk.LabelFrame(self.main_frame, text="设置", padding=(20, 10))
        settings_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=20)
        settings_frame.grid_columnconfigure(0, weight=1)
        
        # 时间设置
        time_frame = ttk.Frame(settings_frame)
        time_frame.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=10)
        
        # 配置time_frame的列权重，使内容居中
        for i in range(5):
            time_frame.grid_columnconfigure(i, weight=1)
        
        # 时间设置控件，使用pack布局使其居中
        time_controls = ttk.Frame(time_frame)
        time_controls.grid(row=0, column=1, columnspan=3)
        
        time_left = ttk.Frame(time_controls)
        time_left.pack(side=tk.LEFT, padx=10)
        ttk.Label(time_left, text="开始时间(秒):", font=("Microsoft YaHei UI", 10)).pack(side=tk.LEFT)
        self.start_time = tk.StringVar(value="0")
        ttk.Entry(time_left, textvariable=self.start_time, width=8).pack(side=tk.LEFT, padx=5)
        
        time_right = ttk.Frame(time_controls)
        time_right.pack(side=tk.LEFT, padx=10)
        ttk.Label(time_right, text="结束时间(秒):", font=("Microsoft YaHei UI", 10)).pack(side=tk.LEFT)
        self.end_time = tk.StringVar(value="0")
        ttk.Entry(time_right, textvariable=self.end_time, width=8).pack(side=tk.LEFT, padx=5)
        
        self.duration_label = ttk.Label(time_controls, text="视频总时长: 0秒", font=("Microsoft YaHei UI", 10))
        self.duration_label.pack(side=tk.LEFT, padx=20)
        
        # 添加分隔线
        separator = ttk.Separator(settings_frame, orient="horizontal")
        separator.grid(row=1, column=0, sticky=(tk.W, tk.E), pady=10, padx=20)
        
        # 尺寸设置
        size_frame = ttk.Frame(settings_frame)
        size_frame.grid(row=2, column=0, sticky=(tk.W, tk.E), pady=10)
        
        # 配置size_frame的列权重，使内容居中
        for i in range(3):
            size_frame.grid_columnconfigure(i, weight=1)
        
        # 尺寸设置控件，使用pack布局使其居中
        size_controls = ttk.Frame(size_frame)
        size_controls.grid(row=0, column=1)
        
        size_left = ttk.Frame(size_controls)
        size_left.pack(side=tk.LEFT, padx=10)
        ttk.Label(size_left, text="目标尺寸:", font=("Microsoft YaHei UI", 10)).pack(side=tk.LEFT)
        self.target_size = tk.StringVar(value="500x500")
        ttk.Entry(size_left, textvariable=self.target_size, width=10).pack(side=tk.LEFT, padx=5)
        
        size_right = ttk.Frame(size_controls)
        size_right.pack(side=tk.LEFT, padx=20)
        self.keep_aspect = tk.BooleanVar(value=True)
        ttk.Checkbutton(size_right, text="保持宽高比", variable=self.keep_aspect).pack(side=tk.LEFT)
        
        # 转换按钮区域
        button_frame = ttk.Frame(self.main_frame)
        button_frame.grid(row=3, column=0, sticky=(tk.W, tk.E), pady=20)
        button_frame.grid_columnconfigure(0, weight=1)  # 让按钮居中
        button_frame.grid_columnconfigure(3, weight=1)
        
        # 转换按钮放在中间的两列
        self.convert_button = tk.Button(button_frame, text="开始转换", command=self.start_convert,
                                      bg="#4CAF50", fg="white",
                                      font=("Microsoft YaHei UI", 10, "bold"),
                                      relief="flat", width=15, height=2)
        self.convert_button.grid(row=0, column=1, padx=10)
        
        # 停止按钮
        self.stop_button = tk.Button(button_frame, text="停止转换", command=self.stop_convert,
                                   bg="#f44336", fg="white",
                                   font=("Microsoft YaHei UI", 10, "bold"),
                                   relief="flat", width=15, height=2,
                                   state=tk.DISABLED)
        self.stop_button.grid(row=0, column=2, padx=10)
        
        # 进度条居中且自适应宽度
        progress_frame = ttk.Frame(self.main_frame)
        progress_frame.grid(row=4, column=0, sticky=(tk.W, tk.E), pady=10)
        progress_frame.grid_columnconfigure(0, weight=1)
        
        self.progress = ttk.Progressbar(progress_frame, mode='determinate', style="Horizontal.TProgressbar")
        self.progress.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=50)  # 添加水平边距
        
        # 状态标签
        self.status_label = ttk.Label(self.main_frame, text="", font=("Microsoft YaHei UI", 10))
        self.status_label.grid(row=5, column=0, sticky=(tk.W, tk.E))
        
        # 预览区域
        preview_frame = ttk.LabelFrame(self.main_frame, text="预览", padding=(10, 5))
        preview_frame.grid(row=6, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), pady=15)
        preview_frame.grid_columnconfigure(0, weight=1)
        preview_frame.grid_rowconfigure(1, weight=1)
        
        # 预览控制按钮居中
        preview_controls = ttk.Frame(preview_frame)
        preview_controls.grid(row=0, column=0, pady=5)
        
        self.preview_button = tk.Button(preview_controls, text="预览GIF", command=self.preview_gif,
                                      bg="#2196F3", fg="white",
                                      font=("Microsoft YaHei UI", 9),
                                      relief="flat", width=10)
        self.preview_button.pack(side=tk.LEFT, padx=5)
        
        # 添加停止预览按钮
        self.stop_preview_button = tk.Button(preview_controls, text="停止预览", command=self.stop_preview,
                                           bg="#FF5722", fg="white",
                                           font=("Microsoft YaHei UI", 9),
                                           relief="flat", width=10,
                                           state=tk.DISABLED)
        self.stop_preview_button.pack(side=tk.LEFT, padx=5)
        
        # 创建预览容器，使用pack布局以保持宽高比
        preview_container = ttk.Frame(preview_frame)
        preview_container.grid(row=1, column=0, sticky=(tk.N, tk.S, tk.E, tk.W), pady=10)
        
        # 设置预览容器的固定大小
        preview_container.configure(width=500, height=500)
        preview_container.grid_propagate(False)  # 防止容器大小被内容改变
        
        # 预览图像标签
        self.preview_label = ttk.Label(preview_container)
        self.preview_label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)  # 居中放置
        
        # 绑定窗口大小变化事件
        self.root.bind('<Configure>', self._on_window_resize)
        
        # 预览控制变量
        self.is_previewing = False
        self.preview_thread = None
        self.is_converting = False
        self.video_duration = 0
        self.should_stop = False
        
        # 配置进度条样式
        self.style.configure("Horizontal.TProgressbar", 
                           background='#4CAF50',
                           troughcolor='#f0f0f0',
                           bordercolor='#f0f0f0',
                           lightcolor='#4CAF50',
                           darkcolor='#4CAF50')
        
    def browse_video(self):
        filename = filedialog.askopenfilename(
            title="选择视频文件",
            filetypes=(("视频文件", "*.mp4 *.avi *.mov"), ("所有文件", "*.*"))
        )
        if filename:
            self.video_path.set(filename)
            # 自动设置输出路径
            output = os.path.splitext(filename)[0] + ".gif"
            self.output_path.set(output)
            
            # 获取视频时长
            try:
                video = VideoFileClip(filename)
                self.video_duration = video.duration
                video.close()
                self.duration_label.config(text=f"视频总时长: {self.video_duration:.1f}秒")
                self.end_time.set(str(int(self.video_duration)))
            except Exception as e:
                self.status_label.config(text=f"无法读取视频时长: {str(e)}")
    
    def browse_output(self):
        filename = filedialog.asksaveasfilename(
            title="选择保存位置",
            defaultextension=".gif",
            filetypes=(("GIF文件", "*.gif"), ("所有文件", "*.*"))
        )
        if filename:
            self.output_path.set(filename)
    
    def start_convert(self):
        if self.is_converting:
            return
            
        # 验证时间输入
        try:
            start = float(self.start_time.get())
            end = float(self.end_time.get())
            if start < 0 or end > self.video_duration or start >= end:
                self.status_label.config(text="请输入有效的时间范围")
                return
        except ValueError:
            self.status_label.config(text="请输入有效的时间值")
            return
            
        # 验证尺寸输入
        try:
            size_str = self.target_size.get()
            if 'x' in size_str:
                width, height = map(int, size_str.split('x'))
                if width <= 0 or height <= 0:
                    raise ValueError("尺寸必须为正数")
            else:
                self.status_label.config(text="请输入有效的尺寸格式 (例如: 500x500)")
                return
        except ValueError as e:
            self.status_label.config(text=f"尺寸格式错误: {str(e)}")
            return
            
        # 重置停止标志
        self.should_stop = False
        
        # 更新按钮状态
        self.convert_button.config(state=tk.DISABLED)
        self.stop_button.config(state=tk.NORMAL)
        
        # 在新线程中运行转换过程
        self.convert_thread = threading.Thread(target=self.convert)
        self.convert_thread.start()
    
    def stop_convert(self):
        if self.is_converting:
            self.should_stop = True
            self.status_label.config(text="正在停止转换...")
    
    def update_progress(self):
        while self.is_converting and not self.should_stop:
            self.progress['value'] = (self.progress['value'] + 1) % 100
            self.root.update()
            time.sleep(0.1)
    
    def convert(self):
        video_path = self.video_path.get()
        output_path = self.output_path.get()
        
        if not video_path or not output_path:
            self.status_label.config(text="请选择输入和输出文件路径")
            self.is_converting = False
            self.convert_button.config(state=tk.NORMAL)
            self.stop_button.config(state=tk.DISABLED)
            return
        
        try:
            self.is_converting = True
            self.status_label.config(text="正在转换中...")
            self.progress['value'] = 0
            
            # 启动进度更新线程
            progress_thread = threading.Thread(target=self.update_progress)
            progress_thread.start()
            
            # 加载视频
            video = VideoFileClip(video_path)
            
            # 截取指定时间段
            start_time = float(self.start_time.get())
            end_time = float(self.end_time.get())
            video = video.subclip(start_time, end_time)
            
            # 调整尺寸
            size_str = self.target_size.get()
            target_width, target_height = map(int, size_str.split('x'))
            
            if self.keep_aspect.get():
                # 保持宽高比
                w, h = video.size
                # 计算缩放比例，确保最大边不超过目标尺寸
                scale = min(target_width/w, target_height/h)
                new_width = int(w * scale)
                new_height = int(h * scale)
                
                # 居中填充到目标尺寸
                final_clip = video.resize((new_width, new_height))
                
                # 如果需要填充，创建黑色背景
                if new_width < target_width or new_height < target_height:
                    from moviepy.editor import ColorClip
                    # 创建黑色背景
                    bg = ColorClip((target_width, target_height), color=(0, 0, 0))
                    bg = bg.set_duration(final_clip.duration)
                    
                    # 计算偏移量使图像居中
                    x_offset = (target_width - new_width) // 2
                    y_offset = (target_height - new_height) // 2
                    
                    # 将视频叠加到背景上
                    final_clip = final_clip.set_position((x_offset, y_offset))
                    final_clip = CompositeVideoClip([bg, final_clip])
            else:
                # 直接调整到目标尺寸
                final_clip = video.resize((target_width, target_height))
            
            # 优化GIF生成参数
            self.status_label.config(text="正在生成GIF，这可能需要一点时间...")
            final_clip.write_gif(
                output_path,
                fps=10,  # 适中的帧率
                program='ffmpeg',  # 使用ffmpeg处理
                opt='optimizeplus',  # 使用优化算法
                fuzz=1,  # 微小的模糊以提高压缩率
                colors=256  # 使用完整的调色板
            )
            
            final_clip.close()
            video.close()
            
            # 检查是否被用户停止
            if self.should_stop:
                self.status_label.config(text="转换已停止")
                # 如果文件已创建，则删除
                if os.path.exists(output_path):
                    try:
                        os.remove(output_path)
                    except:
                        pass
            else:
                self.progress['value'] = 100
                # 显示文件大小
                file_size = os.path.getsize(output_path) / (1024 * 1024)  # 转换为MB
                self.status_label.config(text=f"转换完成！文件大小: {file_size:.1f}MB")
            
            self.is_converting = False
            self.convert_button.config(state=tk.NORMAL)
            self.stop_button.config(state=tk.DISABLED)
            
        except Exception as e:
            self.is_converting = False
            self.convert_button.config(state=tk.NORMAL)
            self.stop_button.config(state=tk.DISABLED)
            self.status_label.config(text=f"转换失败: {str(e)}")
    
    def preview_gif(self):
        if not self.output_path.get() or not os.path.exists(self.output_path.get()):
            self.status_label.config(text="请先转换GIF文件")
            return
        
        # 创建预览窗口
        PreviewWindow(self.root, self.output_path.get())
    
    def stop_preview(self):
        # 这个方法现在可以删除，因为预览窗口有自己的关闭按钮
        pass
    
    def _on_window_resize(self, event):
        """处理窗口大小变化事件"""
        if event.widget == self.root:
            # 获取主窗口当前大小
            width = event.width
            height = event.height
            
            # 计算预览区域的新尺寸
            preview_size = min(500, width - 100, height - 400)  # 留出边距和其他控件的空间
            
            # 更新预览容器的大小
            preview_container = self.preview_label.master
            preview_container.configure(width=preview_size, height=preview_size)
            
            # 如果正在预览，重新调整图像大小
            if hasattr(self, 'current_preview_image'):
                self._resize_preview_image(preview_size)
    
    def _resize_preview_image(self, size):
        """调整预览图像大小"""
        if hasattr(self, 'current_preview_image'):
            w, h = self.current_preview_image.size
            scale = min(size/w, size/h)
            new_size = (int(w*scale), int(h*scale))
            resized = self.current_preview_image.resize(new_size, Image.Resampling.LANCZOS)
            photo = ImageTk.PhotoImage(resized)
            self.preview_label.configure(image=photo)
            self.preview_label.image = photo  # 保持引用

class PreviewWindow:
    def __init__(self, parent, gif_path):
        self.window = tk.Toplevel(parent)
        self.window.title("GIF预览")
        self.window.geometry("600x600")
        self.window.resizable(True, True)
        
        # 设置窗口最小尺寸
        self.window.minsize(400, 400)
        
        # 创建预览容器
        self.preview_frame = ttk.Frame(self.window)
        self.preview_frame.pack(expand=True, fill=tk.BOTH, padx=10, pady=10)
        
        # 创建预览标签
        self.preview_label = ttk.Label(self.preview_frame)
        self.preview_label.pack(expand=True)
        
        # 控制变量
        self.is_previewing = True
        self.frames = []
        
        # 加载GIF
        self.load_gif(gif_path)
        
        # 绑定窗口关闭事件
        self.window.protocol("WM_DELETE_WINDOW", self.on_closing)
        
        # 开始预览
        self.preview_thread = threading.Thread(target=self._preview_loop)
        self.preview_thread.start()
    
    def load_gif(self, gif_path):
        try:
            gif = Image.open(gif_path)
            while True:
                frame = gif.copy()
                # 计算缩放比例，确保图像适应显示区域
                w, h = frame.size
                scale = min(500/w, 500/h)
                new_size = (int(w*scale), int(h*scale))
                frame = frame.resize(new_size, Image.Resampling.LANCZOS)
                self.frames.append(ImageTk.PhotoImage(frame))
                gif.seek(gif.tell() + 1)
        except EOFError:
            pass
        except Exception as e:
            messagebox.showerror("错误", f"加载GIF失败: {str(e)}")
    
    def _preview_loop(self):
        frame_index = 0
        while self.is_previewing:
            try:
                self.preview_label.configure(image=self.frames[frame_index])
                frame_index = (frame_index + 1) % len(self.frames)
                self.window.update()
                time.sleep(0.1)  # 控制播放速度
            except tk.TclError:
                break
    
    def on_closing(self):
        self.is_previewing = False
        self.window.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = VideoToGifConverter(root)
    root.mainloop()